package vd

import (
	"strings"
	"reflect"
	"math/big"
	"kumachan/standalone/ctn"
)


const EnumDummyInterfaceMethod = "Enum"

type GoStructConfig struct {
	Records  [] interface {}
}

type GoStructTransformer struct {
	Transformer
	DetermineType   func(reflect.Type) *Type
	GetReflectType  func(*Type) reflect.Type
}

func GoTypeVdId(t reflect.Type) TypeId {
	var pkg = t.PkgPath()
	var name = t.Name()
	var project = (func() string {
		if strings.Contains(pkg, "/") {
			var segments = strings.Split(pkg, "/")
			var last = (len(segments) - 1)
			var project = segments[last]
			return project
		} else if pkg != "" {
			return pkg
		} else {
			return ""
		}
	})()
	// TODO: fill vendor (specify as an arg) and version (Struct_1version)
	return MakeTypeId("", project, name, "")
}

func CreateGoStructTransformer(opts GoStructConfig) (GoStructTransformer, string) {
	var types = make(map[TypeId] reflect.Type)
	var types_rev = make(map[reflect.Type] TypeId)
	var defaults = make(map[reflect.Type] reflect.Value)
	var interface_ = make(map[reflect.Type] reflect.Type)
	var register = func(t reflect.Type, expected reflect.Kind) {
		if t.Kind() != expected {
			switch expected {
			case reflect.Struct:    panic("struct expected")
			case reflect.Interface: panic("interface expected")
			default:
				panic("something went wrong")
		} }
		var _, registered = types_rev[t]
		if registered {
			return
		}
		var id = GoTypeVdId(t)
		var _, duplicate = types[id]
		if duplicate {
			panic("duplicate type: " + id.String())
		}
		types[id] = t
		types_rev[t] = id
	}
	for _, rec := range opts.Records {
		var struct_t = reflect.TypeOf(rec)
		defaults[struct_t] = reflect.ValueOf(rec)
		register(struct_t, reflect.Struct)
		var m, exists = struct_t.MethodByName(EnumDummyInterfaceMethod)
		if exists {
			if m.Type.NumIn() != 2 { panic("invalid enum dummy method") }
			var interface_t = m.Type.In(1)
			interface_[struct_t] = interface_t
			register(interface_t, reflect.Interface)
		}
	}
	var bool_t = reflect.TypeOf(false)
	var float_t = reflect.TypeOf(float64(0.0))
	var complex_t = reflect.TypeOf(complex128(0.0))
	var integer_t = reflect.TypeOf(big.NewInt(0))
	var string_t = reflect.TypeOf("")
	var binary_t = reflect.TypeOf(([] byte)(nil))
	var determine_type func(reflect.Type)(*Type)
	determine_type = func(t reflect.Type) *Type {
		switch t {
		case bool_t:
			return PrimitiveType(Bool)
		case float_t:
			return PrimitiveType(Float)
		case complex_t:
			return PrimitiveType(Complex)
		case integer_t:
			return PrimitiveType(Integer)
		case string_t:
			return PrimitiveType(String)
		case binary_t:
			return PrimitiveType(Binary)
		default:
			if t.Kind() == reflect.Slice {
				var item = t.Elem()
				return ContainerType(Array, determine_type(item))
			} else if t.Kind() == reflect.Func && (
				t.NumIn() == 0 && t.NumOut() == 2 && t.Out(1) == bool_t) {
				var inner = t.Out(0)
				return ContainerType(Optional, determine_type(inner))
			} else if t.Kind() == reflect.Interface {
				var id, exists = types_rev[t]
				if !(exists) { panic("unregistered type: " + t.String()) }
				return AlgebraicType(Enum, id)
			} else if t.Kind() == reflect.Struct {
				var id, exists = types_rev[t]
				if !(exists) { panic("unregistered type: " + t.String()) }
				return AlgebraicType(Record, id)
			} else {
				panic("unsupported type: " + t.String())
			}
		}
	}
	var get_reflect_type func(*Type)(reflect.Type)
	get_reflect_type = func(t *Type) reflect.Type {
		switch t.kind {
		case Bool:    return bool_t
		case Float:   return float_t
		case Complex: return complex_t
		case Integer: return integer_t
		case String:  return string_t
		case Binary:  return binary_t
		case Array:
			var item_t = get_reflect_type(t.elementType)
			return reflect.SliceOf(item_t)
		case Optional:
			var inner_t = get_reflect_type(t.elementType)
			return reflect.FuncOf([] reflect.Type {},
				[] reflect.Type { inner_t, bool_t }, false)
		case Record:
			var rt, exists = types[t.identifier]
			if !(exists) { panic("unsupported type: " + t.String()) }
			return rt
		case Enum:
			var rt, exists = types[t.identifier]
			if !(exists) { panic("unsupported type: " + t.String()) }
			return rt
		default:
			panic("unsupported type: " + t.String())
		}
	}
	var internal = make(map[TypeId] ctn.Map[string,*Type])
	var internal_add = func(id TypeId, key string, t *Type) {
		var m, exists = internal[id]
		if !(exists) {
			m = ctn.MakeMap[string,*Type](ctn.StringCompare)
		}
		internal[id] = m.Inserted(key, t)
	}
	for _, rec := range opts.Records {
		var struct_t = reflect.TypeOf(rec)
		var struct_id = types_rev[struct_t]
		for i := 0; i < struct_t.NumField(); i += 1 {
			var field = struct_t.Field(i)
			internal_add(struct_id, field.Name, determine_type(field.Type))
		}
		var interface_t, exists = interface_[struct_t]
		if exists {
			var interface_id = types_rev[interface_t]
			var case_key = struct_t.Name()
			internal_add(interface_id, case_key, determine_type(struct_t))
		}
	}
	var serializer = Serializer {
		PrimitiveSerializer: PrimitiveSerializer {
			WriteBool:    func(obj Object) bool { return obj.(bool) },
			WriteFloat:   func(obj Object) float64 { return obj.(float64) },
			WriteComplex: func(obj Object) complex128 { return obj.(complex128) },
			WriteInteger: func(obj Object) *big.Int { return obj.(*big.Int) },
			WriteString:  func(obj Object) string { return obj.(string) },
			WriteBinary:  func(obj Object) ([] byte) { return obj.([] byte) },
		},
		ContainerSerializer: ContainerSerializer {
			IterateArray: func(obj Object, f func(uint, Object) error) error {
				var v = reflect.ValueOf(obj)
				for i := 0; i < v.Len(); i += 1 {
					var elem = v.Index(i)
					err := f(uint(i), elem.Interface())
					if err != nil { return err }
				}
				return nil
			},
			UnwrapOptional: func(obj Object) (Object, bool) {
				var v = reflect.ValueOf(obj)
				if v.IsNil() {
					return nil, false
				}
				var ret = v.Call([] reflect.Value {})
				var ok = ret[1].Interface().(bool)
				if ok {
					var inner = ret[0].Interface()
					return inner, true
				} else {
					return nil, false
				}
			},
		},
		AlgebraicSerializer: AlgebraicSerializer {
			AccessEnum: func(_ TypeId, obj Object) (string,*Type,Object) {
				obj = reflect.ValueOf(obj).Interface()
				var rt = reflect.TypeOf(obj)
				var case_key = rt.Name()
				if case_key == "" { panic("something went wrong") }
				var case_type = determine_type(rt)
				return case_key, case_type, obj
			},
			IterateRecord: func(_ TypeId, obj Object, f func(string,*Type,Object)(error)) error {
				var v = reflect.ValueOf(obj)
				for i := 0; i < v.NumField(); i += 1 {
					var field_info = v.Type().Field(i)
					var field_name = field_info.Name
					var field_type = determine_type(field_info.Type)
					var field_value = v.Field(i).Interface()
					err := f(field_name, field_type, field_value)
					if err != nil { return err }
				}
				return nil
			},
		},
	}
	var deserializer = Deserializer {
		PrimitiveDeserializer: PrimitiveDeserializer {
			ReadBool:    func(obj bool) Object { return obj },
			ReadFloat:   func(obj float64) Object { return obj },
			ReadComplex: func(obj complex128) Object { return obj },
			ReadInteger: func(obj *big.Int) (Object, bool) { return obj, true },
			ReadString:  func(obj string) Object { return obj },
			ReadBinary:  func(obj ([] byte)) Object { return obj },
		},
		ContainerDeserializer: ContainerDeserializer {
			Some: func(inner_obj Object, inner_t *Type) Object {
				var inner_v = reflect.ValueOf(inner_obj)
				var rt = get_reflect_type(ContainerType(Optional, inner_t))
				var v = reflect.MakeFunc(rt, func(args ([] reflect.Value)) ([] reflect.Value) {
					return [] reflect.Value {
						inner_v,
						reflect.ValueOf(true),
					}
				})
				var f = v.Interface()
				return f
			},
			None: func(inner_t *Type) Object {
				var rt = get_reflect_type(ContainerType(Optional, inner_t))
				var v = reflect.MakeFunc(rt, func(args ([] reflect.Value)) ([] reflect.Value) {
					return [] reflect.Value {
						reflect.Value {},
						reflect.ValueOf(false),
					}
				})
				var f = v.Interface()
				return f
			},
			CreateArray: func(element_t *Type) Object {
				var slice_t = reflect.SliceOf(get_reflect_type(element_t))
				var slice_v = reflect.MakeSlice(slice_t, 0, 0)
				var slice = slice_v.Interface()
				return slice
			},
			AppendItem: func(slice_ptr *Object, item Object) {
				var slice_v = reflect.ValueOf(*slice_ptr)
				var item_v = reflect.ValueOf(item)
				var appended_v = reflect.Append(slice_v, item_v)
				var appended = appended_v.Interface()
				*slice_ptr = appended
			},
		},
		AlgebraicDeserializer: AlgebraicDeserializer {
			CheckSupport: func(tid TypeId, k TypeKind) bool {
				var t, exists = types[tid]
				if exists {
					if (k == Record) && (t.Kind() == reflect.Struct)  {
						return true
					} else if (k == Enum) && (t.Kind() == reflect.Interface) {
						return true
					} else {
						return false
					}
				} else {
					return false
				}
			},
			MakeEnum: func(tid TypeId, f func(ctn.Map[string,*Type])(string,Object,error)) (Object,error) {
				var _, obj, err = f(internal[tid])
				return obj, err
			},
			MakeRecord: func(tid TypeId, f func(ctn.Map[string,*Type])(ctn.Map[string,Object],error)) (Object,error) {
				var values, err = f(internal[tid])
				if err != nil { return nil, err }
				var rt = types[tid]
				var ptr_v = reflect.New(rt)
				var default_, has_default = defaults[rt]
				if has_default {
					ptr_v.Elem().Set(default_)
				}
				values.ForEach(func(name string, obj Object) {
					ptr_v.Elem().FieldByName(name).Set(reflect.ValueOf(obj))
				})
				return ptr_v.Elem().Interface(), nil
			},
		},
	}
	var ts = GoStructTransformer {
		Transformer:    Transformer {
			Serializer:   &serializer,
			Deserializer: &deserializer,
		},
		DetermineType:  determine_type,
		GetReflectType: get_reflect_type,
	}
	var src = generateCorrespondingSource(opts.Records, determine_type)
	return ts, src
}


